<?php

namespace maoxuner\LaravelJsonRpc\Server;

use Illuminate\Http\JsonResponse;
use Illuminate\Http\Request;
use Illuminate\Http\Response;
use Illuminate\Support\Arr;
use Illuminate\Support\Collection;
use maoxuner\LaravelJsonRpc\Entities\RequestObject;
use maoxuner\LaravelJsonRpc\Entities\ResponseObject;
use maoxuner\LaravelJsonRpc\Exceptions\ParseException;
use maoxuner\LaravelJsonRpc\Exceptions\RequestException;
use Throwable;

/**
 * JsonRpc request parser / helper
 */
class Parser
{
    protected mixed $extractor = null;

    protected mixed $compactor = null;

    public function __construct()
    {
        $this->extractor = config('jsonrpc.extractor');
        $this->compactor = config('jsonrpc.compactor');
    }

    public static function parse(string|array $payload, bool $validate = true): RequestObject|Collection
    {
        try {
            $calls = is_string($payload) ? json_decode($payload, true) : $payload;

            if (! empty($calls) && is_array($calls)) {
                $batch = Arr::isList($calls);
                if ($batch) {
                    foreach ($calls as &$call) {
                        if (! is_array($call)) {
                            $call = [];
                        }
                    }
                }
                $calls = collect($batch ? $calls : [$calls])
                    ->map($validate
                        ? [RequestObject::class, 'build']
                        : fn ($req) => new RequestObject($req)
                    );

                return $batch ? $calls : $calls->first();
            }
        } catch (ParseException|RequestException $exception) {
            throw $exception;
        } catch (Throwable $throwable) {
            throw new ParseException('', 0, $throwable);
        }
    }

    public function extract(Request $request, bool $validate = true): RequestObject|Collection
    {
        // Avoid trim strings & empty string null conversion
        if (is_callable($this->extractor)) {
            $requestObject = call_user_func($this->extractor, $request, $this);
        } elseif ($request->isJson()) {
            $requestObject = $this->parse($request->getContent(), $validate);
        } else {
            throw new RequestException;
        }

        return $requestObject;
    }

    public function compact(ResponseObject|Collection|null $responseObject, RequestObject|Collection|null $requestObject): JsonResponse|Response
    {
        if (is_callable($this->compactor)) {
            $response = call_user_func($this->compactor, $responseObject, $requestObject);
        } else {
            $response = $responseObject ? response()->json($responseObject) : response()->make();
        }

        return $response;
    }
}
